import { NConfig, NFont } from '../types';
import { createDefaultConfig } from '../utils/create-default-value';
import { NExcel } from '../types';
import { Inject, Injectable } from '../decorators/inject';
import { getPixelRatio } from '../utils/tools';
import { TSelectRanage, TCellControl } from '../types/common.interface';
import { Hook } from './hook';
import { Transform, toJSON, classTransformToObj, objTransformToClass } from '../decorators/toJSON';
import { isGenerateEqual } from '../utils/object-equal';
import { AddHook } from '../decorators/add-hook';
import { v4 } from 'uuid'
type TVisibleBoundary = {row: number, col: number, endRow: number, endCol: number};
type TCellInfo = {x: number, y: number, w: number, h: number};

@Injectable('Config')
@Transform()
export class Config implements NConfig.IConfig {
    public defaultFont:NFont.IFont = {};
    public currentRenderId: string = '';
    public readonly defaultConf: any = null;
    @toJSON([])
    public cellControls: TCellControl[] = [];
    @toJSON({}, {
        toObj: (val: NConfig.ICommomConfig, instance: Config) => {
            if(isGenerateEqual(instance.defaultConf.common, val)) {
                return undefined;
            } else {
                return val;
            }
        }
    })
    public common: NConfig.ICommomConfig;
    public header: NConfig.IHeader;
    @toJSON(100)
    public excuteMaxCommandStore = 100;
    public view: NConfig.IView;
    @toJSON({row: 500000,col: 500000})
    public allowMaxRender = {
        row: 500000,
        col: 500000
    }
    @toJSON({row: 300, col: 50})
    public defaultMaxScroll = {
        row: 300,
        col: 50
    }
    protected renderCellInfo: Map<number, Map<number, {x: number,y: number,w: number,h: number}>> = new Map();
    containerSize: {
        height: number;
        width: number;
    }
    @toJSON(1)
    public scale: number = 1;
    public pixelRatio: number;
    public visibleBoundary: TVisibleBoundary = {row: 0, col: 0, endRow: 0, endCol: 0};
    @toJSON([], {
        toObj: (val: Set<number>) => {
            return Array.from(val);
        },
        toCls: (val: number[]) => {
            return new Set(val);
        }
    })
    public hiddenRows: Set<number> = new Set();
    @toJSON([], {
        toObj: (val: Set<number>) => {
            return Array.from(val);
        },
        toCls: (val: number[]) => {
            return new Set(val);
        }
    })
    public hiddenCols: Set<number> = new Set();
    @toJSON({}, {
        toObj: (value: Map<number, Set<number>>) => {
            let result: {[key: number]: number[]} = {};
            value.forEach((arr, row) => {
                if(arr.size > 0) {
                    result[row] = Array.from(arr)
                }
            });
            return result;
        },
        toCls: (_: any, val: {[key: string]: number[]}) => {
            let map: Map<number, Set<number>> = new Map();
            for(let row in val) {
                if(val[row].length > 0) {
                    map.set(Number(row), new Set(val[row]))
                }
            }
            return map;
        }
    })
    public hiddenCells: Map<number, Set<number>> = new Map();
    public enlarge  = 0;
    protected currentSelected: TSelectRanage = null;
    @toJSON({row: -1, col: -1}, {
        name: 'freeze'
    })
    // protected _freeze  = {row: 8, col: 3};
    // protected _freeze  = {row: 2, col: -1};
    // protected _freeze  = {row: 0, col: -1};
    // protected _freeze  = {row: 12, col: -1};
    protected _freeze = {row: -1, col: -1};
    // protected _freeze = {row: 8, col: 3};
    public get freeze () {
        return {...this._freeze};
    } 
    public set freeze(val) {
        this._freeze = val;
    }
    @toJSON({}, {
        toObj: (val: any, instance: Config) => {
            if(isGenerateEqual(instance.defaultConf.header, val)) {
                return undefined;
            } else {
                return val;
            }
        }
    })
    protected _header: any = null;
    @toJSON({top: 2, right: 2, bottom: 2,left: 2})
    public defaultPadding = {
        top: 2,
        right: 2,
        bottom: 2,
        left: 2
    }
    constructor(
        @Inject(NExcel.CanvasToken) protected readonly CanvasEle: HTMLCanvasElement,
        protected hook: Hook
    ) {
        // this.hiddenRows.add(3);
    }
    init() {
        this.pixelRatio = getPixelRatio(this.CanvasEle.getContext('2d'));
        // @ts-ignore
        this.defaultConf = createDefaultConfig({},1);
        this._header = this.defaultConf.header;
        this.header = this.defaultConf.header;
        this.listenPixelRadioChange(this.pixelRatio);
        this.view = this.defaultConf.view;
        // this.view = {
        //     // row: 125 - 47, col: 198 - 78
        // }
        this.common = this.defaultConf.common;
        this.defaultFont = {
            ...this.common.font,
            size: this.common.font.size * this.pixelRatio * this.scale,
            lineHeight: this.common.font.lineHeight * this.pixelRatio * this.scale,
        }
        this.cellControls.push({
            type: 'full',
            position: -1,
            span: -1,
            cellType: 'text',
            font: this.common.font,
            fill: {}
        });
        this.containerSize = {
            height: this.CanvasEle.height,
            width: this.CanvasEle.width
        }
        this.hook.beforeDataRender.add(() => {
            this.renderCellInfo.clear();
        });
    }
    public getMaxX() {
        return Math.ceil(this.CanvasEle.width * this.pixelRatio);
    }
    public getMaxY() {
        return Math.ceil(this.CanvasEle.height * this.pixelRatio);
    }
    public updateFreezeRow(row: number) {
        this.view.row = 0;
        this.hook.beforeChangeFreezeRow.excute(row);
        this._freeze.row = row;
        this.hook.afterChangeFreezeRow.excute(row);
    }
    public updateFreezeCol(col: number) {
        this.view.col = 0;
        this.hook.beforeChangeFreezeCol.excute(col);
        this._freeze.col = col;
        this.hook.afterChangeFreezeCol.excute(col);
    }
    public getTableSize() {
        return {
            width: this.CanvasEle.width * this.pixelRatio,
            height: this.CanvasEle.height * this.pixelRatio
        }
    }
    public setRenderCellInfo(row: number, col: number, info: TCellInfo) {
        let rowInfo: Map<number, TCellInfo>;
        if(this.renderCellInfo.has(row)) {
            rowInfo = this.renderCellInfo.get(row);
        } else {
            rowInfo = new Map();
            this.renderCellInfo.set(row, rowInfo);
        }
        rowInfo.set(col, info);
    }
    public getRenderCellInfo(row: number, col: number): [(null|string), TCellInfo] {
        try {
            let res = this.renderCellInfo.get(row).get(col);
            if(res === undefined) {
                throw new Error(`没有找到row：${row}、col: ${col}，的渲染位置信息！`);
            }
            return [null, res];
        } catch (error) {
            console.log(error)
            return ['error!', {x: -1, y: -1, w: 0, h: 0}];
        }
    }
    public setCurrentSelectedRange(selected: TSelectRanage) {
        this.currentSelected = selected;
    }
    public getCurrentSelectedRange() {
        return this.currentSelected;
    }
    @AddHook('pixelRatioChange')
    protected listenPixelRadioChange(pixelRatio: number) {
        this.header = {
            row: {
                font: {
                    ...this._header.row.font,

                },
                fill: this._header.row.fill,
                height: this._header.row.height * pixelRatio * this.scale,
                width: this._header.row.width * pixelRatio * this.scale
            },
            col: {
                font: this._header.col.font,
                fill: this._header.col.fill,
                height: this._header.col.height * pixelRatio * this.scale,
                width: this._header.col.width * pixelRatio * this.scale
            }
        }
        if(this.common) {
            this.defaultFont = {
                ...this.common.font,
                size: this.common.font.size * pixelRatio * this.scale,
                lineHeight: this.common.font.lineHeight * pixelRatio * this.scale,
            }
        }
        this.enlarge = pixelRatio * this.scale;
    }
    @AddHook('afterScaleChange')
    protected listenScaleChange() {
        this.listenPixelRadioChange(this.pixelRatio);
        this.enlarge = this.pixelRatio * this.scale;
    }
    public updateScale(scale: number) {
        this.hook.beforeScaleChange.excute(scale);
        this.scale = scale;
        this.hook.afterScaleChange.excute(scale);
    }
    @AddHook('afterDeleteRow')
    protected cellControlsChangeByDeleteRow(row: number) {
        this.cellControls = this.cellControls.map(item => { 
            if(item.type === 'row') {
                if(item.position < row) {
                    return item;
                }
                if(item.position === row) {
                    if(item.span === 1) {
                        return null;
                    } else {
                        item.position = item.position - 1;
                        item.span = item.span - 1;
                        return item;
                    }
                }
                if(item.position + item.span - 1 > row) {
                    item.position = item.position - 1;
                }
                return item;
            } else {
                return item;
            }
        }).filter(item => item !== null);
    }

    @AddHook('afterDeleteRow')
    protected hiddenRowChangeByDeleteRow(row: number) {
        this.hiddenRows = (() => {
            let rows = Array.from(this.hiddenRows);
            return new Set(rows.map(item => {
                if(item < row) {
                    return item;
                }
                if(item === row) {
                    return null;
                }
                if(item > row) {
                    return item - 1;
                }
            }).filter(item => item !== null));
        })();
    }

    @AddHook('afterDeleteRow')
    protected hiddenCellsChangeByDeleteRow(row: number) {
        this.hiddenCells.delete(row);
        this.hiddenCells.forEach((cells, mapRow) => {
            if(mapRow > row) {
                this.hiddenCells.delete(mapRow);
                this.hiddenCells.set(mapRow - 1, cells);
            }
        });
    }

    @AddHook('afterInsertRow')
    protected cellControlsChangeByAddRow(row: number) {
        this.cellControls = this.cellControls.map(item => {
            if(item.type === 'row') {
                if(item.position <= row) {
                    return item;
                }
                if(item.position > row) {
                    item.position = item.position + 1;
                }
            } else {
                return item;
            }
        });
    }

    @AddHook('afterInsertRow')
    protected hiddenRowsByAddRow(row: number) {
        this.hiddenRows = (() => {
            let rows = Array.from(this.hiddenRows);
            return new Set(rows.map(item => {
                if(item <= row) {
                    return item;
                }
                if(item > row) {
                    return item + 1;
                }
            }).filter(item => item !== null));
        })();
    }

    @AddHook('afterInsertRow')
    protected hiddenCellsByAddRow(row: number) {
        this.hiddenCells.forEach((cells, mapRow) => {
            if(mapRow > row) {
                this.hiddenCells.delete(mapRow);
                this.hiddenCells.set(mapRow + 1, cells);
            }
        });
    }

    @AddHook('afterDeleteCol')
    protected cellControlsChangeByDeleteCol(col: number) {
        this.cellControls = this.cellControls.map(item => { 
            if(item.type === 'col') {
                if(item.position < col) {
                    return item;
                }
                if(item.position === col) {
                    if(item.span === 1) {
                        return null;
                    } else {
                        item.position = item.position - 1;
                        item.span = item.span - 1;
                        return item;
                    }
                }
                if(item.position + item.span - 1 > col) {
                    item.position = item.position - 1;
                }
                return item;
            } else {
                return item;
            }
        }).filter(item => item !== null);
    }

    @AddHook('afterDeleteCol')
    protected hiddenColChangeByDeleteCol(col: number) {
        this.hiddenCols = (() => {
            let cols = Array.from(this.hiddenCols);
            return new Set(cols.map(item => {
                if(item < col) {
                    return item;
                }
                if(item === col) {
                    return null;
                }
                if(item > col) {
                    return item - 1;
                }
            }).filter(item => item !== null));
        })();
    }

    @AddHook('afterDeleteCol')
    protected hiddenCellsChangeByDeleteCol(col: number) {
        this.hiddenCells.forEach((cells, row) => {
            let cols = Array.from(cells).sort();
            let newCells = new Set(cols.map(item => {
                if(item === col) {
                    return null;
                }
                if(item > col) {
                    return item - 1;
                }
                return item;
            }).filter(item => item !== null));
            if(newCells.size === 0) {
                this.hiddenCells.delete(row);
            } else {
                this.hiddenCells.set(row, newCells);
            }
        });
    }
}